///////////////////////////////////////////////////////////////////////////////
// HDR Effects
// NextGenFX - HellRaven Mods
///////////////////////////////////////////////////////////////////////////////

#include "MODULES/global.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////

shared float4 TextureOffset : TEXTUREANIMOFFSET;

///////////////////////////////////////////////////////////////////////////////

static const int MAX_SAMPLES = 32; // Maximum texture grabs

// Contains sampling offsets used by the techniques
shared float4 cavSampleOffsets[MAX_SAMPLES] : cavSampleOffsets;
shared float4 cavSampleWeights[MAX_SAMPLES] : cavSampleWeights;
shared float4 cvBlurParams                  : cvBlurParams;

float ScreenResolutionX; // Screen.fWidth  (injected by shaderloader)
float ScreenResolutionY; // Screen.fHeight (injected by shaderloader)
float AspectRatio;       // Screen.fAspectRatio

float2 texelSizeL0;
float2 texelSizeL1;
float2 texelSizeL2;
float2 texelSizeL3;

///////////////////////////////////////////////////////////////////////////////
// Samplers
///////////////////////////////////////////////////////////////////////////////

sampler PERLINNOISE_SAMPLER = sampler_state
{
    AddressU  = WRAP;
    AddressV  = WRAP;
    MIPFILTER = LINEAR;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

DECLARE_TEXTURE(DIFFUSEMAP_TEXTURE)
sampler DIFFUSE_SAMPLER = sampler_state
{
    ASSIGN_TEXTURE(DIFFUSEMAP_TEXTURE)
    AddressU  = CLAMP;
    AddressV  = CLAMP;
    MIPFILTER = NONE;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

DECLARE_TEXTURE(DEPTHBUFFER_TEXTURE)
sampler DEPTHBUFFER_SAMPLER = sampler_state
{
    ASSIGN_TEXTURE(DEPTHBUFFER_TEXTURE)
    AddressU  = CLAMP;
    AddressV  = CLAMP;
    MIPFILTER = NONE;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

DECLARE_TEXTURE(MISCMAP1_TEXTURE)
sampler MISCMAP1_SAMPLER = sampler_state
{
    ASSIGN_TEXTURE(MISCMAP1_TEXTURE)
    AddressU  = CLAMP;
    AddressV  = CLAMP;
    MIPFILTER = LINEAR;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

DECLARE_TEXTURE(MISCMAP2_TEXTURE)
sampler MISCMAP2_SAMPLER = sampler_state
{
    ASSIGN_TEXTURE(MISCMAP2_TEXTURE)
    AddressU  = CLAMP;
    AddressV  = CLAMP;
    MIPFILTER = LINEAR;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

///////////////////////////////////////////////////////////////////////////////
// Structs
// Note: VS_INPUT_WORLD removed -- declared but never referenced.
///////////////////////////////////////////////////////////////////////////////

struct VS_INPUT_SCREEN
{
    float4 position : POSITION;
    float4 texcoord : TEXCOORD;
};

struct VtoP
{
    float4 position : POSITION;
    float4 tex      : TEXCOORD0;
};

struct DepthSpriteOut
{
    float4 colour : COLOR0;
    float  depth  : DEPTH0;
};

///////////////////////////////////////////////////////////////////////////////

VtoP vertex_shader_passthru(const VS_INPUT_SCREEN IN)
{
    VtoP OUT;
    OUT.position = IN.position;
    OUT.tex      = IN.texcoord;
    return OUT;
}

///////////////////////////////////////////////////////////////////////////////
// Blur Passes
///////////////////////////////////////////////////////////////////////////////

float4 PS_ReflectionBlur(const VtoP IN) : COLOR
{
    // SM3.0: full sample array in a single loop.
    // 0.75 factor preserves the original accumulation behaviour.
    float4 result = 0;
    [loop]
    for (int i = 0; i < MAX_SAMPLES; ++i)
        result += cavSampleWeights[i] * tex2D(DIFFUSE_SAMPLER, IN.tex.xy + cavSampleOffsets[i].xy);
    return result * 0.75f;
}

///////////////////////////////////////////////////////////////////////////////

float4 GetBase(float2 texUV)
{
    return tex2D(DIFFUSE_SAMPLER, texUV);
}

// Weighted accumulation blur using engine-provided offsets and weights.
// Named "GaussBlur5x5" for engine technique compatibility -- the actual
// sample count and kernel are controlled by the engine via cavSampleWeights.
float4 PS_GaussBlur5x5(const VtoP IN) : COLOR
{
    static const int SAMPLE_COUNT = 20;
    float4 result = 0;
    [loop]
    for (int i = 0; i < SAMPLE_COUNT; ++i)
        result += cavSampleWeights[i] * GetBase(IN.tex.xy + cavSampleOffsets[i].xy);
    return result;
}

///////////////////////////////////////////////////////////////////////////////

float4 PS_DownScale4x4(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
    static const int SAMPLE_COUNT = 16;
    float4 result = 0;
    [loop]
    for (int i = 0; i < SAMPLE_COUNT; ++i)
        result += GetBase(vScreenPosition + cavSampleOffsets[i].xy);
    return result / SAMPLE_COUNT;
}

///////////////////////////////////////////////////////////////////////////////

float4 PS_DownScale2x2(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
    static const int SAMPLE_COUNT = 4;
    float4 result = 0;
    [loop]
    for (int i = 0; i < SAMPLE_COUNT; ++i)
        result += tex2D(DIFFUSE_SAMPLER, vScreenPosition + cavSampleOffsets[i].xy);
    return result / SAMPLE_COUNT;
}

///////////////////////////////////////////////////////////////////////////////
// DownScale2x2ForMotionBlur
//
// First stage of the motion blur pipeline. Downscales the frame and writes
// a depth-derived blur intensity into the alpha channel. That alpha is later
// read by PS_CompositeBlur (visualtreatment.h) as blurIntensity.
//
// RAMP-UP
//   sqrt(rawDepth) gives a convex ramp: blur onset is gradual as speed builds
//   rather than snapping on at a threshold.
//
// FOREGROUND PROTECTION
//   Objects very close to the camera receive reduced blur via a depth-based
//   mask. Prevents near-field geometry (bonnet, barriers at point-blank range)
//   from appearing smeared at low speeds.
//   smoothstep(0, 0.25, depth): nearest 25% of depth range is de-blurred.
//
// PRE-EMPHASIS
//   sample.w is frame alpha, typically 1.0 in Carbon's backbuffer.
//   The net effect is a fixed ~1.375x brightness lift on the downscaled RGB
//   that compensates for darkening from blur accumulation. Intentional.
///////////////////////////////////////////////////////////////////////////////

float4 PS_DownScale2x2ForMotionBlur(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
    float4 sample     = tex2D(DIFFUSE_SAMPLER,     vScreenPosition);
    float  depthValue = tex2D(DEPTHBUFFER_SAMPLER, vScreenPosition).x;

    // Pre-emphasis brightness lift.
    float motionStrength = saturate(sample.w) * 0.75f;
    sample.xyz = lerp(sample.xyz, sample.xyz * (1.0f + motionStrength), 0.5f);

    // Convert depth (0-1) to a normalised scene-distance factor.
    float depthFactor = 1.0f / max(0.0001f, 1.0f - depthValue);
    float distStart   = cvBlurParams.x;
    float distEnd     = cvBlurParams.y * 200.0f;
    float range       = distEnd - distStart;
    float slope       = -1.0f / (abs(range) < 0.001f ? 0.001f : range);
    float intercept   = -distEnd * slope;
    float rawDepth    = saturate(depthFactor * slope + intercept);

    // Convex ramp for smooth speed onset.
    float scaledDepth = sqrt(rawDepth);

    // Foreground protection: progressively remove blur from near objects.
    float foregroundMask = smoothstep(0.0, 0.25, depthValue);
    scaledDepth *= foregroundMask;

    return float4(sample.xyz, scaledDepth);
}

///////////////////////////////////////////////////////////////////////////////
// Luminance Downscale  (Auto-Exposure source pass)
//
// Computes geometric-mean (log-average) luminance across 16 taps.
// Output is packed into all four channels for flexible CPU readback.
//
// Geometric mean is used because human brightness perception is logarithmic:
// a scene "half as bright" should yield half the exposure, not half the
// linear sum. Arithmetic mean over-weights bright highlights.
///////////////////////////////////////////////////////////////////////////////

float4 PS_LuminanceDownscale(const VtoP IN) : COLOR
{
    static const int SAMPLE_COUNT = 16;
    float logLumSum = 0.0;

    [loop]
    for (int i = 0; i < SAMPLE_COUNT; ++i)
    {
        float3 c   = GetBase(IN.tex.xy + cavSampleOffsets[i].xy).rgb;
        // Clamp to epsilon before log to avoid -inf from black pixels.
        float  lum = max(dot(c, LUMINANCE_VECTOR), 0.0001);
        logLumSum += log(lum);
    }

    // Geometric mean: exponent of mean log-luminance.
    float avgLum = exp(logLumSum / float(SAMPLE_COUNT));

    return float4(avgLum, avgLum, avgLum, 1.0);
}

///////////////////////////////////////////////////////////////////////////////
// Bloom
///////////////////////////////////////////////////////////////////////////////

#define BLOOM_EXPOSURE             0.75f
#define BLOOM_SPREAD_MULTIPLIER    1.25f
#define BLOOM_BRIGHTPASS_THRESHOLD 0.675f

// Pre-computed Uncharted 2 white-point normalisation factor.
// Moved outside tonemap_bloom so it is a true compile-time constant
// rather than being re-evaluated every pixel invocation.
//   1.0 / ( (W*(A*W+C*B)+D*E) / (W*(A*W+B)+D*F) - E/F )
//   with A=0.22 B=0.50 C=0.10 D=0.20 E=0.015 F=0.20 W=11.20
static const float BLOOM_TONEMAP_WHITE = 1.294921f;

// Uncharted 2 filmic curve for the bloom accumulation buffer.
// Keeps bloom highlights from blowing out while staying perceptually warm.
float3 tonemap_bloom(float3 col)
{
    const float A = 0.22f;
    const float B = 0.50f;
    const float C = 0.10f;
    const float D = 0.20f;
    const float E = 0.015f;
    const float F = 0.20f;

    col *= BLOOM_EXPOSURE;
    col  = ((col * (A * col + C * B) + D * E) / (col * (A * col + B) + D * F)) - E / F;
    col *= BLOOM_TONEMAP_WHITE;
    return col;
}

// Pre-normalised Gaussian weights for 16 taps (sum ~= 1.0).
static const float weights[16] =
{
    0.19859f, 0.18306f, 0.15176f, 0.11306f, 0.07684f, 0.04834f,
    0.02812f, 0.01524f, 0.00776f, 0.00373f, 0.00170f, 0.00073f,
    0.00030f, 0.00012f, 0.00004f, 0.00001f
};

// PASS 1: Horizontal Gaussian Blur + Bright-pass
float4 PS_Bloom_Horizontal_HQ(const VtoP IN) : COLOR
{
    float  threshold = BLOOM_BRIGHTPASS_THRESHOLD;
    float  knee      = threshold * 0.75f;

    float3 center   = tex2D(DIFFUSE_SAMPLER, IN.tex).rgb;
    float  bright0  = dot(center, LUMINANCE_VECTOR);
    float  contrib0 = smoothstep(threshold - knee, threshold + knee, bright0);
    float3 bloomSum = center * contrib0 * weights[0];

    [loop]
    for (int i = 1; i < 16; ++i)
    {
        float2 offset = float2(texelSizeL2.x * i * BLOOM_SPREAD_MULTIPLIER, 0.0f);

        float3 cR    = tex2D(DIFFUSE_SAMPLER, IN.tex + offset).rgb;
        float  contR = smoothstep(threshold - knee, threshold + knee, dot(cR, LUMINANCE_VECTOR));
        bloomSum    += cR * contR * weights[i];

        float3 cL    = tex2D(DIFFUSE_SAMPLER, IN.tex - offset).rgb;
        float  contL = smoothstep(threshold - knee, threshold + knee, dot(cL, LUMINANCE_VECTOR));
        bloomSum    += cL * contL * weights[i];
    }

    return float4(tonemap_bloom(bloomSum), 1.0f);
}

// PASS 2: Vertical Gaussian Blur
// Counterpart to Pass 1. Together they form a separable 2D Gaussian.
// Not wired into a technique -- requires a SunRise hook or engine patch
// to add the second call. Uncomment the technique block below when ready.
float4 PS_Bloom_Vertical_HQ(const VtoP IN) : COLOR
{
    float3 bloomSum = tex2D(DIFFUSE_SAMPLER, IN.tex).rgb * weights[0];

    [loop]
    for (int i = 1; i < 16; ++i)
    {
        float2 offset = float2(0.0f, texelSizeL2.y * i * BLOOM_SPREAD_MULTIPLIER);
        bloomSum += tex2D(DIFFUSE_SAMPLER, IN.tex + offset).rgb * weights[i];
        bloomSum += tex2D(DIFFUSE_SAMPLER, IN.tex - offset).rgb * weights[i];
    }

    return float4(tonemap_bloom(bloomSum), 1.0f);
}

///////////////////////////////////////////////////////////////////////////////

float4 PS_BlendTextures(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
    float4 result;
    result  = tex2D(DIFFUSE_SAMPLER,  vScreenPosition) * cavSampleWeights[0];
    result += tex2D(MISCMAP1_SAMPLER, vScreenPosition) * cavSampleWeights[1];
    return result;
}

///////////////////////////////////////////////////////////////////////////////

float4 PS_CombineReflectionColourAndHeadlightAlpha(in float2 vScreenPosition : TEXCOORD0) : COLOR
{
    float4 result;
    result.xyz = tex2D(DIFFUSE_SAMPLER,  vScreenPosition).xyz;
    result.w   = dot(tex2D(MISCMAP1_SAMPLER, vScreenPosition).xyz, LUMINANCE_VECTOR);
    return result;
}

///////////////////////////////////////////////////////////////////////////////
// Smoke Buffers
///////////////////////////////////////////////////////////////////////////////

struct PrepBuffersOut
{
    float4 colour0 : COLOR0;
    float4 colour1 : COLOR1;
    float  depth   : DEPTH0;
};

struct VS_PassThru_OUT
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD;
};

VS_PassThru_OUT VS_PassThru(const VS_INPUT_SCREEN IN)
{
    VS_PassThru_OUT OUT;
    OUT.position = IN.position;
    OUT.texcoord = IN.texcoord;
    return OUT;
}

PrepBuffersOut PS_PrepSmokeBuffers(in float2 texCoord : TEXCOORD)
{
    PrepBuffersOut OUT;

    OUT.colour0 = float4(tex2D(DIFFUSE_SAMPLER,    texCoord).xyz, 0);
    OUT.colour1 = float4(tex2D(PERLINNOISE_SAMPLER, texCoord).xyz, 0);

    // 24-bit depth unpack: R = high 8 bits, G = mid 8 bits, B = low 8 bits.
    float3 depthColor  = tex2D(DEPTHBUFFER_SAMPLER, texCoord).rgb;
    depthColor        *= float3(65536 * 255.0 / 16777215.0,
                                  256 * 255.0 / 16777215.0,
                                        255.0 / 16777215.0);
    depthColor        += float3(1.0 / 4096.0, 1.0 / 1048576.0, 0);
    OUT.depth          = dot(depthColor, float3(1, 1, 1));

    return OUT;
}

struct CompositeOut
{
    float4 colour : COLOR0;
    float  depth  : DEPTH0;
};

CompositeOut PS_CompositeSmoke(in float2 texCoord : TEXCOORD0)
{
    CompositeOut OUT;
    OUT.depth = tex2D(DEPTHBUFFER_SAMPLER, texCoord).x;

    float4 backbuffer  = tex2D(DIFFUSE_SAMPLER,    texCoord);
    float2 perlinNoise = tex2D(PERLINNOISE_SAMPLER, texCoord).xy * 2.0 - 1.0;

    // Distortion expressed in texels rather than a fixed UV fraction.
    // At any resolution, kSmokeDistortionTexels pixels of max displacement
    // gives a visually consistent result. At 1280x720 this is ~1/32 of the
    // screen width, matching the old /30.0 behaviour. At higher resolutions
    // the UV shrinks proportionally so the smoke doesn't grow with pixel count.
    static const float kSmokeDistortionTexels = 40.0;
    float4 solidSmoke  = tex2D(MISCMAP1_SAMPLER,
                               texCoord + perlinNoise * texelSizeL0 * kSmokeDistortionTexels);

    OUT.colour.xyz = lerp(backbuffer.xyz, solidSmoke, saturate(solidSmoke.w * 1.1));
    OUT.colour.w   = solidSmoke.w * 0.5;

    return OUT;
}

///////////////////////////////////////////////////////////////////////////////
// Techniques
///////////////////////////////////////////////////////////////////////////////

technique CompositeSmoke
{
    pass p0
    {
        VertexShader = compile vs_3_0 VS_PassThru();
        PixelShader  = compile ps_3_0 PS_CompositeSmoke();
    }
}

technique PrepSmokeBuffers
{
    pass p0
    {
        VertexShader = compile vs_3_0 VS_PassThru();
        PixelShader  = compile ps_3_0 PS_PrepSmokeBuffers();
    }
}

technique TwoPassBlur
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_ReflectionBlur();
    }
}

technique GaussBlur5x5
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_GaussBlur5x5();
    }
}

technique DownScale4x4
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_DownScale4x4();
    }
}

technique DownScale2x2
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_DownScale2x2();
    }
}

technique DownScale2x2ForMotionBlur
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_DownScale2x2ForMotionBlur();
    }
}

technique DownScaleForBloom
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_Bloom_Horizontal_HQ();
    }
}

// Uncomment when a second bloom pass can be wired in via SunRise or engine patch.
// technique DownScaleForBloom_Vertical
// {
//     pass p0
//     {
//         VertexShader = compile vs_3_0 vertex_shader_passthru();
//         PixelShader  = compile ps_3_0 PS_Bloom_Vertical_HQ();
//     }
// }

// LuminanceAverage: renders geometric-mean scene luminance to a small target.
// Call after DownScale4x4 so the input is already a reduced frame.
// Shaderloader reads back via GetRenderTargetData, applies EMA smoothing,
// and injects the result as gAverageLuminance into visualtreatment each frame.
technique LuminanceAverage
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader = compile ps_3_0 PS_LuminanceDownscale();
    }
}

technique BlendTextures
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_BlendTextures();
    }
}

technique CombineReflectionColourAndHeadlightAlpha
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_passthru();
        PixelShader  = compile ps_3_0 PS_CombineReflectionColourAndHeadlightAlpha();
    }
}
